﻿#region Using

using System.Collections.Generic;
using System.IO;

using DotNetMatrix;

#endregion

namespace EigenFaces {
	public class Classifier {
		private Dictionary<string, List<Matrix>> _classes = new Dictionary<string, List<Matrix>>();

		private IClassReduceStrategy _reduceStrategy = new ClassReduceStrategyFurthestFromCurrent();

		public double MinThreshold { get; set; }
		public double MaxThreshold { get; set; }
		public int MaxClassSize { get; set; }

		public IClassReduceStrategy ReduceStrategy {
			get { return _reduceStrategy; }
			set {
				if( value != null ) {
					_reduceStrategy = value;
				}
			}
		}

		public Classifier() {
			MinThreshold = 1.25;
			MaxThreshold = 2.5;
			MaxClassSize = 10;
		}

		private double GetThresHold(int n) {
			double deltaThres = MaxThreshold - MinThreshold;
			double posX = (double)(n - 2) / (MaxClassSize - 2);
			return (1 - posX) * deltaThres + MinThreshold;
		}


		public string Classify(Matrix trait) {
			var className = string.Empty;
			var distance = double.MaxValue;

			foreach( var pair in _classes ) {
				var d = trait.MinEuclidDistance(pair.Value);
				if( d < distance ) {
					distance = d;
					className = pair.Key;
				}
			}

			if( !string.IsNullOrWhiteSpace(className) && distance < GetThreshold(className) ) {
				return className;
			}
			return null;
		}

		public void LearnTraits(Matrix trait, string className) {
			if( string.IsNullOrWhiteSpace(className) ) {
				return;
			}

			if( !_classes.ContainsKey(className) ) {
				_classes[className] = new List<Matrix>();
			}

			while( _classes[className].Count >= MaxClassSize ) {
				_reduceStrategy.Reduce(_classes[className], trait);
			}

			_classes[className].Add(trait);
		}

		public void Clear() {
			_classes.Clear();
		}

		public bool RemoveClass(string className) {
			return _classes.Remove(className);
		}

		public Dictionary<string, List<Matrix>> InnerMap {
			get { return _classes; }
			set { _classes = value; }
		}

		private double GetThreshold(string className) {
			return _classes[className].GetAverage().MaxEuclidDistance(_classes[className]) * GetThresHold(_classes[className].Count);
		}

		public void ReadFrom(BinaryReader reader) {
			Clear();
			MaxClassSize = reader.ReadInt32();
			MinThreshold = reader.ReadDouble();
			MaxThreshold = reader.ReadDouble();
			int dictcount = reader.ReadInt32();
			for( int di = 0; di < dictcount; di++ ) {
				var list = new List<Matrix>();
				string key = reader.ReadString();
				int listcount = reader.ReadInt32();
				for( int li = 0; li < listcount; li++ ) {
					list.Add(Matrix.ReadFrom(reader));
				}
				_classes.Add(key, list);
			}
		}

		public void WriteTo(BinaryWriter writer) {
			writer.Write(MaxClassSize);
			writer.Write(MinThreshold);
			writer.Write(MaxThreshold);
			writer.Write(_classes.Count);
			foreach( var pair in _classes ) {
				writer.Write(pair.Key);
				writer.Write(pair.Value.Count);
				foreach( var m in pair.Value ) {
					m.WriteTo(writer);
				}
			}
		}

	}
}
